/*
 * rom_hal.c
 *
 * Created: 8/16/2013 10:24:36 AM
 *  Author: Ken Arok
 *
 * \file
 *
 * \brief Hardware Abstraction Layer of Read Only Memory (EEPROM or Flash ROM).
 *
 * Copyright (c) 2013 PT Hanindo Automation Solutions. All rights reserved.
 *
 */ 

#include "config_board.h"

#if BOARD_1_0_USED

#include <stdbool.h>

#include "spi.h"
#include "delay.h"
#include "rom_hal.h"
#include "compiler.h"

#if ROM_PART_NAME_SST25VF080B

// Instruction code
#define SST25VF080B_WREN			0x06	//!< Write Enable
#define SST25VF080B_WRDI			0x04	//!< write Disable
#define SST25VF080B_EWSR			0x50	//!< Enable Write Status Register
#define SST25VF080B_RDID			0x9F	//!< Read Identification
#define SST25VF080B_RDSR			0x05	//!< Read Status Register
#define SST25VF080B_WRSR			0x01	//!< Write Status Register
#define SST25VF080B_READ			0x03	//!< Read Data Bytes
#define SST25VF080B_FAST_READ		0x0B	//!< Read Data Bytes at Higher Speed
#define SST25VF080B_PP				0x02	//!< Page Program
#define SST25VF080B_SE				0xD8	//!< Sector Erase
#define SST25VF080B_BE				0xC7	//!< Bulk Erase
#define SST25VF080B_DP				0xB9	//!< Deep Power Down
#define SST25VF080B_RES				0xAB	//!< Release from Deep Power Down
#define SST25VF080B_AAI				0xAD	//!< Auto Address Increnment Programming
#define SST25VF080B_ERASE4			0x20	//!< Erase 4 KByte block of memory array
#define SST25VF080B_ERASE32			0x52	//!< Erase 32 KByte block of memory array
#define SST25VF080B_ERASE64			0xD8	//!< Erase 64 KByte block of memory array

#elif ROM_PART_NAME_M25P10A
#error Place another ROM chip macro here!
#endif /* ROM_PART_NAME_SST25VF080B */


/* Data flash SPI characteristic options. */
static const spi_channel_options_t DATAFLASH_SPI_OPTIONS = {
	.baudrate = 12000000,	//!> 12 MHz clock frequency.
	.bits = 8,				//!> 8-bit data length.
	.delay_bs = 3,			//!> 3 x 30ns = 90ns TCES.
	.delay_bct = 1,			//!> 1 x 1us = 1us
	.keep_actv = 1,			//!> Drive CS logic manualy.
	.spi_mode = SPI_MODE_0,	//!> Mode 0 of SPI.
	.master_setup = true	//!> Setup in master mode.
};

static spi_descriptor_t spi_rom_desc;

/*! \name Private function.
 */
//! @{

#if ROM_PART_NAME_SST25VF080B

/** \brief Read status register of SST25VF080B.
 *
 * \return SREG	Status register of SST25VF080B.
 *
 */
static uint8_t sst25vf080b_read_status_register(void)
{
	const uint8_t _rdsr[1] = {SST25VF080B_RDSR};
	uint8_t _sreg;
	
	while(SPI_STATUS_OK != spi_select_chip(spi_rom_desc));
	spi_write_byte(spi_rom_desc, (const void *)_rdsr, 1);
	spi_read_byte(spi_rom_desc, (void *)&_sreg, 1);
	while(SPI_STATUS_OK != spi_deselect_chip(spi_rom_desc));

	return _sreg;
}


/** \brief Enable write to SST25VF080B.
 */
static void sst25vf080b_write_enable(void)
{
	const uint8_t _wren[1] = {SST25VF080B_WREN};
	
	while(SPI_STATUS_OK != spi_select_chip(spi_rom_desc));
	spi_write_byte(spi_rom_desc, (const void *)_wren, 1);
	while(SPI_STATUS_OK != spi_deselect_chip(spi_rom_desc));
}

/** \brief Enable write to SST25VF080B.
 */
static void sst25vf080b_enable_write_status_register(void)
{
	const uint8_t _ewsr[1] = {SST25VF080B_EWSR};
	
	while(SPI_STATUS_OK != spi_select_chip(spi_rom_desc));
	spi_write_byte(spi_rom_desc, (const void *)_ewsr, 1);
	while(SPI_STATUS_OK != spi_deselect_chip(spi_rom_desc));
}


/** \brief Disable write to SST25VF080B.
 */
static void sst25vf080b_write_disable(void)
{
	const uint8_t _wrdi[1] = {SST25VF080B_WRDI};

	while(SPI_STATUS_OK != spi_select_chip(spi_rom_desc));
	spi_write_byte(spi_rom_desc, (const void *)_wrdi, 1);
	while(SPI_STATUS_OK != spi_deselect_chip(spi_rom_desc));
}


/** \brief Write status register to SST25VF080B.
 *
 * \param _sreg_val		Status register to be written.
 *
 */
static void sst25vf080b_write_status_register(uint8_t _sreg_val)
{
	const uint8_t _wrsr[1] = {SST25VF080B_WRSR};
	
	/* Enable Write Status Register. */
	sst25vf080b_enable_write_status_register();
	
	while(SPI_STATUS_OK != spi_select_chip(spi_rom_desc));
	spi_write_byte(spi_rom_desc, (const void *)_wrsr, 1);
	spi_write_byte(spi_rom_desc, (const void *)&_sreg_val, 1);
	while(SPI_STATUS_OK != spi_deselect_chip(spi_rom_desc));
}

/** \brief Write block protection lock down on SST25VF080B.
 *
 * \param _bp	Block Protection Lock Down.
 *
 */
static void sst25vf080b_write_block_protection_lock_down(uint8_t _bp)
{
	uint8_t _bp_bit;
	
	_bp_bit = (_bp & 0x07) << 2;
	
	/* Unlock memory protection area. */
	sst25vf080b_write_status_register(_bp_bit);	
}


/** \brief Program SST25VF080B using AAI word program.
 *
 * \param _star_addr	Start of address to be programmed.
 * \param _pbuf			Pointer to the data want to be programmed.
 * \param _len			Number of bytes of data want to be programmed.
 *
 * \return Status
 *	\retval 0	Success.
 *	\retval	-1	Failed.
 *
 */
static char sst25vf080b_auto_address_increment_word_program(uint32_t _star_addr, const void *_pbuf, uint32_t _len)
{
	const uint8_t _aai[1] = {SST25VF080B_AAI};
	uint8_t _addr[3];
	uint8_t _sreg, *_ptr;
	unsigned long i;
	
	if(_pbuf == NULL) return -1;
	
	_ptr = (uint8_t *)_pbuf;
	
	_addr[2] = (uint8_t)_star_addr;
	_addr[1] = (uint8_t)(_star_addr >> 8);
	_addr[0] = (uint8_t)(_star_addr >> 16);
	
	/* Enable write operation. */
	sst25vf080b_write_enable();
	
	/* Wait until device is not busy. */
	do {
		_sreg = sst25vf080b_read_status_register();
	}while(_sreg & 0x01);
	
	i = 0;
	if(_len > 1) {
		while(SPI_STATUS_OK != spi_select_chip(spi_rom_desc));
		spi_write_byte(spi_rom_desc, (const void *)_aai, 1);
		spi_write_byte(spi_rom_desc, (const void *)_addr, 3);
		spi_write_byte(spi_rom_desc, (const void *)(_ptr + i), 2);
		while(SPI_STATUS_OK != spi_deselect_chip(spi_rom_desc));
		i = i + 2;
		_len = _len - 2;
		
		while(_len)
		{
			/* Wait until device is not busy. */
			do {
				_sreg = sst25vf080b_read_status_register();
			}while(_sreg & 0x01);
			
			while(SPI_STATUS_OK != spi_select_chip(spi_rom_desc));
			spi_write_byte(spi_rom_desc, (const void *)_aai, 1);
			spi_write_byte(spi_rom_desc, (const void *)(_ptr + i), 2);
			while(SPI_STATUS_OK != spi_deselect_chip(spi_rom_desc));
			i = i + 2;
			_len = _len - 2;
		}

		/* Wait until device is not busy. */
		do {
			_sreg = sst25vf080b_read_status_register();
		}while(_sreg & 0x01);
		
		/* Exit AAI mode. */	
		sst25vf080b_write_disable();
		
		_sreg = sst25vf080b_read_status_register();
	}
	
	/* Lock memory block protection. */
	sst25vf080b_write_block_protection_lock_down(0x07);
	
	return 0;
}

/** \brief Erase 32 KB block memory on SST25VF080B.
 *
 * \param _block_addr	Block address want to be erased.
 *
 */
static void sst25vf080b_block_erase_32kb(uint32_t _block_addr)
{
	const uint8_t _erase[1] = {SST25VF080B_ERASE32};
	uint8_t _addr[3];
	uint8_t _sreg;

	_block_addr = _block_addr << 15;

	_addr[2] = (uint8_t)_block_addr;
	_addr[1] = (uint8_t)(_block_addr >> 8);
	_addr[0] = (uint8_t)(_block_addr >> 16);

	/* Unlock memory block protection. */
	sst25vf080b_write_block_protection_lock_down(0);
	
	/* Enable write operation. */
	sst25vf080b_write_enable();
	
	/* Wait until device is not busy. */
	do {
		_sreg = sst25vf080b_read_status_register();
	}while(_sreg & 0x01);	
	
	while(SPI_STATUS_OK != spi_select_chip(spi_rom_desc));
	spi_write_byte(spi_rom_desc, (const void *)_erase, 1);
	spi_write_byte(spi_rom_desc, (const void *)_addr, 3);
	while(SPI_STATUS_OK != spi_deselect_chip(spi_rom_desc));
	delay_ms(100);
}


/** \brief Read identification register of SST25VF080B.
 *
 * \return ID_REG	32 bit identification register.
 *
 */
static uint32_t sst25vf080b_read_identification(void)
{
	const uint8_t _rdid[1] = {SST25VF080B_RDID};
	uint8_t _data[3];
	uint8_t _sreg;	
	uint32_t _id;

	/* Wait until device is not busy. */
	do {
		_sreg = sst25vf080b_read_status_register();
	}while(_sreg & 0x01);	
	
	while(SPI_STATUS_OK != spi_select_chip(spi_rom_desc));
	spi_write_byte(spi_rom_desc, (const void *)_rdid, 1);
	spi_read_byte(spi_rom_desc, (void *)_data, 3);
	while(SPI_STATUS_OK != spi_deselect_chip(spi_rom_desc));
	
	_id = _data[0];
	_id = _id << 8;
	_id |= _data[1];
	_id = _id << 8;
	_id |= _data[2];
	
	return _id;
	
}


/** \brief Read data byte from SST25VF080B.
 *
 * \param _start_addr	Start address in the memory want to be read.
 * \param _pbuf			Pointer to the buffer where data will be copied.
 * \param _len			Number of byte data want to be read.
 *
 * \return Status
 *	\retval	0	Success.
 *	\retval	-1	Failed.
 *
 */
static char sst25vf080b_read_data_bytes(uint32_t _start_addr, void *_pbuf, uint32_t _len)
{
	const uint8_t _read[1] = {SST25VF080B_READ};
	uint8_t _addr[3];
	uint8_t _sreg;
	
	if(_pbuf == NULL) return -1;
		
	_addr[2] = (uint8_t)_start_addr;
	_addr[1] = (uint8_t)(_start_addr >> 8);
	_addr[0] = (uint8_t)(_start_addr >> 16);
	
	/* Wait until device is not busy. */
	do {
		_sreg = sst25vf080b_read_status_register();
	}while(_sreg & 0x01);
	
	while(SPI_STATUS_OK != spi_select_chip(spi_rom_desc));
	spi_write_byte(spi_rom_desc, (const void *)_read, 1);
	spi_write_byte(spi_rom_desc, (const void *)_addr, 3);
	spi_read_byte(spi_rom_desc, _pbuf, _len);
	while(SPI_STATUS_OK != spi_deselect_chip(spi_rom_desc));
	
	return 0;
}

#elif ROM_PART_NAME_M25P10A
#error Place another ROM chip definition here!

#endif /* ROM_PART_NAME_SST25VF080B */

//! @}

/* ----------------------------------------- Public function ---------------------------------------- */


void rom_init(void)
{
	const char spi_rom_port[] = "SPIROM";

	/* Open SPI port. */
	spi_rom_desc = spi_open(spi_rom_port, 0);
	if(spi_rom_desc) {
		/* Setup SPI Interface for Serial Flash */
		spi_setup(spi_rom_desc, &DATAFLASH_SPI_OPTIONS);
	}
}


uint32_t rom_read_id(void)
{
#if ROM_PART_NAME_SST25VF080B
	return sst25vf080b_read_identification();
#endif /* ROM_PART_NAME_SST25VF080B */
}


char rom_read(uint32_t _start_addr, void *_pbuf, uint32_t _len)
{
#if ROM_PART_NAME_SST25VF080B
	return sst25vf080b_read_data_bytes(_start_addr, _pbuf, _len);
#endif
}


char rom_write(uint32_t _star_addr, const void *_pbuf, uint32_t _len)
{
#if ROM_PART_NAME_SST25VF080B
	return sst25vf080b_auto_address_increment_word_program(_star_addr, _pbuf, _len);
#endif /* ROM_PART_NAME_SST25VF080B */
}


void rom_erase_block(uint32_t _block_addr)
{
#if ROM_PART_NAME_SST25VF080B
	sst25vf080b_block_erase_32kb(_block_addr);
#endif
}

#endif /* BOARD_1_0_USED */
